package com.cdn.submit.aop;

import com.cdn.submit.anno.ReSubmit;
import com.cdn.submit.exception.ReSubmitExectpion;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

/**
 * 蔡定努
 * 2024/06/30 15:02
 */
@Aspect
public class Asp {
    private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();


    private final RedissonClient redisClient;

    @Autowired
    public Asp(RedissonClient redisClient) {
        this.redisClient = redisClient;
    }


    @Around("@annotation(com.cdn.submit.anno.ReSubmit)")
    public Object interceptor(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        ReSubmit reSubmit = method.getAnnotation(ReSubmit.class);
        final String lockkey = getLockKey(joinPoint, method, reSubmit);
        RLock lock = redisClient.getLock(lockkey);
        boolean success = lock.tryLock(-1, reSubmit.expire(), reSubmit.timeUnit());
        if (!success) {
            throw new ReSubmitExectpion(reSubmit.message());
        }
        try {
            return joinPoint.proceed();
        } finally {
            if (lock.isLocked()) {
                lock.unlock();
            }
        }
    }


    private String getLockKey(ProceedingJoinPoint joinPoint, Method method, ReSubmit reSubmit) {
        // 获取方法参数
        final Object[] args = joinPoint.getArgs();
        // 获取Method对象上所有的注解
        final Parameter[] parameters = method.getParameters();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < parameters.length; i++) {
            sb.append(reSubmit.delimiter()).append(args[i]);
        }
        // 判断表达式
        StandardEvaluationContext context = getEvaluationContext(parameters, args);
        String[] key = reSubmit.key();
        StringBuilder keysb = new StringBuilder();

        for (String s : key) {
            Expression expression = EXPRESSION_PARSER.parseExpression(s);
            String value = expression.getValue(context, String.class);
            keysb.append(reSubmit.delimiter()).append(value);
        }
        return joinPoint.getTarget().getClass().getName() + method.getName()
                + reSubmit.prefix() + (keysb.length() > 0?keysb:sb);
    }


    /**
     * 获取方法上的参数
     *
     * @param args 变量
     * @return {SimpleEvaluationContext}
     */
    private StandardEvaluationContext getEvaluationContext(Parameter[] parameters, Object[] args) {
        StandardEvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            // 获取参数名称
            String paramName = parameter.getName();
            // 获取参数值
            Object paramValue = args[i];
            // 将参数名称和参数值设置到SpEL表达式上下文中
            context.setVariable(paramName, paramValue);
        }
        return context;
    }
}
